#include "aki/jsbind.h"
#include "vlc/vlc.h"
#include "vlc/libvlc_media_player.h"
#include <fcntl.h>
#include <unistd.h>

std::map<std::string, int> g_TypeList= {
    {"S16N", 16}
};

class VedioData {
public:
    VedioData(size_t with, size_t height, std::string format = "RV24")
    {
        with_ = with;
        height_ = height;
        format_ = format;
        int buffSize = with_ * height_ * 3 * sizeof(uint8_t);
        buffer_ = (uint8_t*) malloc(buffSize);
    }
    
    ~VedioData()
    {
        AKI_LOG(INFO) << "~VedioData";
        if (buffer_ != nullptr) {
            free(buffer_);
            buffer_ = nullptr;
        }
        
        if (fd_ != -1) {
            close(fd_);
        } 
    }
    
    unsigned GetWith() const
    {
        return with_;
    }
    
    unsigned GetHeiht() const 
    {
        return height_;
    }
    
    uint8_t * GetBuffer()
    {
        return buffer_;
    }
    
    void SetFd(int fd)
    {
        fd_ = fd;
    }
    
    int GetFd() const
    {
        return fd_;
    }
    
    void lock()
    {
        mutex_.lock();
    }
    
    void unlock()
    {
        mutex_.unlock();
    }
    
    std::string GetFormat() const
    {
        return format_;
    }
    
    private:
    int fd_ = -1;
    uint8_t *buffer_ = nullptr;
    size_t with_ = 0;
    size_t height_ = 0;
    std::mutex mutex_;
    std::string format_;
};

void *lock(void *opaque, void **planes)
{
    auto vedioData = (VedioData*)opaque;
    vedioData->lock();
    if (!vedioData->GetFormat().compare(std::string("I420"))) {
        planes[0] = vedioData->GetBuffer();
        size_t ySize = vedioData->GetHeiht() * vedioData->GetWith();
        size_t uvSize = vedioData->GetHeiht() * vedioData->GetWith() / 4;
        planes[1] = vedioData->GetBuffer() + ySize;
        planes[2] = vedioData->GetBuffer() + ySize + uvSize;
    } else {
        *planes = vedioData->GetBuffer();
    }
    
    return nullptr;
}
    
void unlock(void *opaque, void *pictire, void *const *planes)
{
   auto vedioData = (VedioData*)opaque;
    vedioData->unlock();
}

void display(void *opaque, void *picture)
{
    auto vedioData = (VedioData*)opaque;
    if (vedioData->GetFd() < 0) {
        return;
    }
    
    size_t byteSize = 0;
    
    if (!vedioData->GetFormat().compare(std::string("I420"))) {
        byteSize = vedioData->GetHeiht() * vedioData->GetWith() * 3 / 2;
    } else {
        byteSize = vedioData->GetHeiht() * vedioData->GetWith() * 3;
    }
    
    write(vedioData->GetFd(), vedioData->GetBuffer(), byteSize);
}
    
unsigned format_setup(void** opaque, char* chroma, unsigned* width, unsigned* height, \
                                unsigned* pitches, unsigned* lines) {
    VedioData* vedioData = static_cast<VedioData*>(*opaque);
    if (!vedioData || !chroma || !width || !height || !pitches || !lines) {
        return 0;
    }
    
    *width = vedioData->GetWith();
    *height = vedioData->GetHeiht();
    
    if (!vedioData->GetFormat().compare(std::string("I420"))) {
        pitches[0] = vedioData->GetWith();
        pitches[1] = vedioData->GetWith() / 2;
        pitches[2] = vedioData->GetWith() / 2;
        lines[0] = vedioData->GetHeiht();
        lines[1] = vedioData->GetHeiht() / 2;
        lines[2] = vedioData->GetHeiht() / 2;
    } else {
        *pitches = vedioData->GetWith();
        *lines = vedioData->GetHeiht();
    }
    memcpy(chroma, vedioData->GetFormat().c_str(), vedioData->GetFormat().size());
    return 1;
}

class AudioData {
public:
    AudioData(int rate, int channels, std::string format)
    {
        sampleRate_ = rate;
        channels_ = channels;
        format_ = format;
        samples_ = (void *)malloc(sampleRate_ * channels_ * GetBitSize() / 8);
    }
    
    ~AudioData()
    {
        AKI_LOG(INFO) << "~AudioData";
        if (samples_ != nullptr) {
            free(samples_);
            samples_ = nullptr;
        }
        if (fd_ != -1) {
            close(fd_);
        }
    }
    
    void SetFd(int fd)
    {
        fd_ = fd;
    }
    
    int GetFd() const
    {
        return fd_;
    }
    
    void *GetData()
    {
        return samples_;
    }
    
    int GetSampleRate() const
    {
        return sampleRate_;
    }
    
    int GetChannels() const
    {
        return channels_;
    }
    
    std::string GetFormat() const
    {
        return format_;
    }
    
    int GetBitSize()
    {
        return g_TypeList[format_];
    }
    
private:
    void *samples_;
    int fd_ = -1;
    int sampleRate_;
    int channels_;
    std::string format_;
};

void AudioPlay(void* opaque, const void* samples, unsigned int count, long pts)
{
    AudioData *audioData = (AudioData *)opaque;
    if (audioData->GetFd() < 0) {
        AKI_LOG(INFO) << "audio open fd failed";
        return;
    }
    size_t byteSize = audioData->GetChannels() * audioData->GetBitSize() / 8 * count;
    write(audioData->GetFd(), samples, byteSize);
}

void AudioPause(void *opaque, long pts)
{
    
}

void AudioResume(void *opaque, long pts)
{
    
}

void AudioFlush(void *opaque, long pts)
{
    
}

void AudioDrain(void *opaque)
{
    
}

int AudioFormatSetup(void **data, char *format, unsigned *rate, unsigned *channels)
{
    if (data == nullptr || format == nullptr || rate == nullptr || channels == nullptr) {
        return -1;
    }
    AudioData *audioData = (AudioData *)(*data);
    memcpy(format, audioData->GetFormat().c_str(), audioData->GetFormat().size());
    *rate = audioData->GetSampleRate();
    *channels = audioData->GetChannels();
    return 0;
}

void StartVlc(std::string path)
{
    std::thread t([path](){
//         const char* vlc_args[] = {
//             "--file-logging",
//             "--logfile=data/storage/el2/base/files/vlc-log.log",
//             "--verbose=4"
//         };
        std::string newPcm = "/data/storage/el2/base/files/new.pcm";
        std::string newYuv = "/data/storage/el2/base/files/new.yuv";
        
        auto vedioData = new VedioData(640, 480, "I420");
        auto audioDate = new AudioData(44100, 2, "S16N");
        
//         libvlc_instance_t *vlc_ins = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args);
        libvlc_instance_t *vlc_ins = libvlc_new(0, nullptr);
        
        vedioData->SetFd(open(newYuv.c_str(), O_WRONLY | O_APPEND | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR));
        audioDate->SetFd(open(newPcm.c_str(), O_WRONLY | O_APPEND | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR));
        
        libvlc_media_t *media = libvlc_media_new_location(vlc_ins, path.c_str());
        
        libvlc_media_player_t *player = libvlc_media_player_new_from_media(media);
        
        libvlc_video_set_format_callbacks(player, format_setup, nullptr);
        //原库的源码amem.c +165 把格式写死了为S16N,这里配置成其他格式会导致解码失败，在amem.c +193有判断，非S16N会失败
        libvlc_audio_set_format_callbacks(player,  AudioFormatSetup, nullptr);
        
        libvlc_video_set_callbacks(player, lock, unlock, display, vedioData);
        libvlc_audio_set_callbacks(player, AudioPlay, AudioPause, AudioResume, AudioFlush, AudioDrain, audioDate);
        
        libvlc_media_player_set_media(player, media);
        libvlc_media_player_play(player);
        
        libvlc_state_t state = libvlc_NothingSpecial;
        do {
            state = libvlc_media_player_get_state(player);
            if (state == libvlc_Error || state == libvlc_Ended) {
                break;
            }
            usleep(20000);
        } while (true);
        
        libvlc_media_player_stop(player);
        libvlc_media_player_release(player);
        libvlc_media_release(media);
        libvlc_release(vlc_ins);
        delete vedioData;
        delete audioDate;
        audioDate = nullptr;
        media = nullptr;
        player = nullptr;
        vlc_ins = nullptr;
        vedioData = nullptr;
    });
    t.detach();
}

JSBIND_GLOBAL()
{
    JSBIND_FUNCTION(StartVlc);
}

JSBIND_ADDON(entry);